package cn.iocoder.yudao.module.esim.service.esasset;

import cn.hutool.core.collection.CollUtil;
import cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil;
import cn.iocoder.yudao.framework.common.util.date.DateUtils;
import cn.iocoder.yudao.module.esim.controller.app.esasset.vo.AppEsAssetActiveReqVO;
import cn.iocoder.yudao.module.esim.controller.app.esasset.vo.AppEsAssetGetQrCodeReqVO;
import cn.iocoder.yudao.module.esim.dal.dataobject.esproduct.EsProductDO;
import cn.iocoder.yudao.module.esim.dal.redis.esasset.EsAssetLockRedisDAO;
import cn.iocoder.yudao.module.esim.enums.ErrorCodeConstants;
import cn.iocoder.yudao.module.esim.enums.esasset.EsAssetActiveStateEnum;
import cn.iocoder.yudao.module.esim.service.esapi.IESimThirtyApiService;
import cn.iocoder.yudao.module.esim.service.esapi.model.CheckUsageRes;
import cn.iocoder.yudao.module.esim.service.esapi.model.CreateOrderReq;
import cn.iocoder.yudao.module.esim.service.esapi.model.CreateOrderRes;
import cn.iocoder.yudao.module.esim.service.esproduct.EsProductService;
import cn.iocoder.yudao.module.esim.util.ESimUtil;
import cn.iocoder.yudao.module.esim.util.QRCodeUtil;
import cn.iocoder.yudao.module.product.api.sku.ProductSkuApi;
import cn.iocoder.yudao.module.product.api.sku.dto.ProductSkuRespDTO;
import cn.iocoder.yudao.module.trade.api.order.dto.TradeOrderRespDTO;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;

import org.springframework.transaction.annotation.Transactional;
import org.springframework.validation.annotation.Validated;

import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.Arrays;
import java.util.List;
import java.util.Set;
import java.util.concurrent.Callable;
import java.util.stream.Collectors;

import cn.iocoder.yudao.module.esim.controller.admin.esasset.vo.*;
import cn.iocoder.yudao.module.esim.dal.dataobject.esasset.EsAssetDO;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;

import cn.iocoder.yudao.module.esim.dal.mysql.esasset.EsAssetMapper;

import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
import static cn.iocoder.yudao.module.esim.enums.ErrorCodeConstants.*;

/**
 * eSim资产 Service 实现类
 *
 * @author 超管
 */
@Service
@Validated
@Slf4j
public class EsAssetServiceImpl implements EsAssetService {
    private static final long CREATE_ESIM_TIMEOUT_MILLIS = 60 * DateUtils.SECOND_MILLIS * 5;
    private static final long ACTIVE_ESIM_TIMEOUT_MILLIS = 60 * DateUtils.SECOND_MILLIS * 2;

    @Resource
    private IESimThirtyApiService simThirtyApiService;
    @Resource
    private ProductSkuApi skuApi;
    @Resource
    private ESimOrderApiProxy orderApiProxy;
    @Resource
    private EsProductService esProductService;
    @Resource
    private EsAssetLockRedisDAO assetLockRedisDAO;
    @Resource
    private EsAssetMapper esAssetMapper;

    @Override
    public String createEsAsset(EsAssetSaveReqVO createReqVO) {
        // 插入
        EsAssetDO esAsset = BeanUtils.toBean(createReqVO, EsAssetDO.class);
        esAssetMapper.insert(esAsset);
        // 返回
        return esAsset.getId();
    }

    @Override
    public void updateEsAsset(EsAssetSaveReqVO updateReqVO) {
        // 校验存在
        validateEsAssetExists(updateReqVO.getId());
        // 更新
        EsAssetDO updateObj = BeanUtils.toBean(updateReqVO, EsAssetDO.class);
        esAssetMapper.updateById(updateObj);
    }

    @Override
    public void deleteEsAsset(String id) {
        // 校验存在
        validateEsAssetExists(id);
        // 删除
        esAssetMapper.deleteById(id);
    }

    private void validateEsAssetExists(String id) {
        if (esAssetMapper.selectById(id) == null) {
            throw exception(ES_ASSET_NOT_EXISTS);
        }
    }

    private EsAssetDO validateEsAssetExists(String id, Long userId) {
        EsAssetDO assetDO = esAssetMapper.getAssetByUserId(userId, id);
        if (assetDO == null) {
            throw exception(ES_ASSET_NOT_EXISTS);
        }
        return assetDO;
    }

    @Override
    public EsAssetDO getEsAsset(String id) {
        return esAssetMapper.selectById(id);
    }

    @Override
    public EsAssetDO getEsAsset(Long userId, String id) {
        return esAssetMapper.getAssetByUserId(userId, id);
    }

    @Override
    public PageResult<EsAssetDO> getEsAssetPage(EsAssetPageReqVO pageReqVO) {
        return esAssetMapper.selectPage(pageReqVO);
    }

    @Override
    public List<EsAssetDO> listByActiveStatus(Long userId, List<EsAssetActiveStateEnum> activeStateEnumList, Set<String> iccids) {
        return esAssetMapper.listByActiveStatus(userId, activeStateEnumList, iccids);
    }

    @Override
    public EsAssetDO getActivatedESim(Long userId, Set<String> iccids) {
        List<EsAssetDO> assetDOList = listByActiveStatus(userId, Arrays.asList(EsAssetActiveStateEnum.ACTIVATED), iccids);
        EsAssetDO activatedESim = null;
        if (CollUtil.isNotEmpty(assetDOList)) {
            if (assetDOList.size() > 1) {
                log.error("存在多个激活状态的eSim, userId = {}, assetIds = {}",
                        userId, assetDOList.stream().map(v -> v.getId()).collect(Collectors.toList()));
                throw ServiceExceptionUtil.exception(ErrorCodeConstants.ES_ASSET_DEFAULT_ERROR, "存在多个激活状态的eSim");
            }
            activatedESim = assetDOList.get(0);
        }

        if (activatedESim != null) {
            // 更新使用量
            try {
                updateUsage(activatedESim);
            } catch (Exception e) {
                log.error(String.format("更新使用量异常, assetId = %s", activatedESim.getId()), e);
            }
        }

        return activatedESim;
    }

    // 更新使用量
    private void updateUsage(EsAssetDO activatedESim) {
        final EsAssetDO assetDO = new EsAssetDO();
        assetDO.setId(activatedESim.getId());

        // 查询使用量
        log.info("创建eSim-查询使用量-开始, assetId = {}", assetDO.getId());
        final CheckUsageRes checkUsageRes = simThirtyApiService.checkUsage(assetDO.getId());
        log.info("创建eSim-查询使用量-成功, checkUsageRes = {}", checkUsageRes);

        // 使用量
        assetDO.setTotalDataSizeInMb(checkUsageRes.getTotalDataSizeInMB());
        assetDO.setUsedDataSizeInMb(checkUsageRes.getUsedDataSizeInMB());

        // 更新开始和结束日期
        final LocalDateTime startDate = ESimUtil.convertToBeijingTime(checkUsageRes.getStartDateUTC());
        final LocalDateTime endDate = ESimUtil.convertToBeijingTime(checkUsageRes.getEndDateUTC());
        assetDO.setStartDate(startDate);
        assetDO.setEndDate(endDate);
        esAssetMapper.updateById(assetDO);
        log.info("创建eSim-更新开始和结束日期, assetId = {}", assetDO.getId());

        activatedESim.setTotalDataSizeInMb(checkUsageRes.getTotalDataSizeInMB());
        activatedESim.setUsedDataSizeInMb(checkUsageRes.getUsedDataSizeInMB());
        activatedESim.setStartDate(startDate);
        activatedESim.setEndDate(endDate);
    }

    @Override
    public int setName(Long userId, String assetId, String name) {
        return esAssetMapper.updateName(userId, assetId, name);
    }

    @Override
    public void setInUse(Long userId, String assetId) {
        // 校验存在
        validateEsAssetExists(assetId, userId);

        // 使用userId加锁防止并发问题
        assetLockRedisDAO.lockUserWhenActive(userId, ACTIVE_ESIM_TIMEOUT_MILLIS, new Callable<Void>() {
            @Override
            public Void call() throws Exception {
                setInUse0(userId, assetId);
                return null;
            }
        });
    }

    @Transactional
    public void setInUse0(Long userId, String assetId) {
        final int updateInUseRes = esAssetMapper.updateInUse(userId, assetId, true);
        log.info("设置为在用, assetId = {}, updateInUseRes = {}", assetId, updateInUseRes);

        final int updateOtherInUseRes = esAssetMapper.updateInUseExcludeAssetId(userId, assetId, false);
        log.info("将其他的设置为不在用, assetId = {}, updateOtherInUseRes = {}", assetId, updateOtherInUseRes);
    }

    /**
     * 激活eSim
     *
     * @param reqVO
     */
    @Override
    public void activeESim(Long userId, AppEsAssetActiveReqVO reqVO) {
        // 校验存在
        final EsAssetDO assetDO = validateEsAssetExists(reqVO.getId(), userId);
        final EsAssetActiveStateEnum activeStateEnum = EsAssetActiveStateEnum.fromValue(assetDO.getActiveState());
        if (activeStateEnum == EsAssetActiveStateEnum.ACTIVATED) {
            log.warn("不能重复激活, assetId = {}", assetDO.getId());
            throw ServiceExceptionUtil.exception(ErrorCodeConstants.ES_ASSET_DEFAULT_ERROR, "不能重复激活");
        }

        // 使用userId加锁防止并发问题
        assetLockRedisDAO.lockUserWhenActive(userId, ACTIVE_ESIM_TIMEOUT_MILLIS, new Callable<Void>() {
            @Override
            public Void call() throws Exception {
                activeESim0(userId, reqVO);
                return null;
            }
        });
    }

    @Transactional
    public void activeESim0(Long userId, AppEsAssetActiveReqVO reqVO) {
        final String assetId = reqVO.getId();

        // 激活当前的eSim
        final int activeRes = esAssetMapper.active(userId, reqVO);
        log.info("激活当前的eSim, assetId = {}, activeRes = {}", assetId, activeRes);

        final List<EsAssetDO> activeAssetList = esAssetMapper.listByActiveStatus(userId, Arrays.asList(EsAssetActiveStateEnum.ACTIVATED), 2, null);
        log.info("查询激活状态的eSim是否只有1个, activeAssetList.size = {}", activeAssetList.size());

        if (activeAssetList.size() == 1) {
            // 如果只有1个，设置为：在用
            final EsAssetDO activeAssetDO = activeAssetList.get(0);
            final int updateInUseRes = esAssetMapper.updateInUse(userId, activeAssetDO.getId(), true);
            log.info("设置为在用, assetId = {}, updateInUseRes = {}", activeAssetDO.getId(), updateInUseRes);
        }
    }

    /**
     * 创建eSim
     *
     * @param orderNo    订单流水号
     * @param buyerEmail 购买用户的邮箱地址
     */
    @Override
    public String createESim(String orderNo, Long userId, String buyerEmail) {
        log.info("创建eSim-开始, orderNo = {}, buyerEmail = {}", orderNo, buyerEmail);

        final String assetId = assetLockRedisDAO.lockOrder(orderNo, CREATE_ESIM_TIMEOUT_MILLIS, new Callable<String>() {
            @Override
            public String call() throws Exception {
                // 查询资产
                final EsAssetDO existedAsset = esAssetMapper.getAssetByOrderNo(orderNo);
                if (existedAsset != null) {
                    log.warn("创建eSim-已创建, orderNo = {}, assetId = {}", orderNo, existedAsset.getId());
                    return existedAsset.getId();
                }

                // 查询订单
                log.info("创建eSim-查询订单, orderNo = {}", orderNo);
                final TradeOrderRespDTO order = orderApiProxy.getOrderByNo(orderNo);
                final Integer paymentPrice = order.getPaymentPrice();
                if (!order.getPayStatus()) {
                    log.warn("创建eSim-订单未支付, orderNo = {}", orderNo);
                    throw ServiceExceptionUtil.exception(ErrorCodeConstants.ES_ASSET_DEFAULT_ERROR, String.format("订单未支付 [%s]", orderNo));
                }

                if (!order.getUserId().equals(userId)) {
                    log.warn("创建eSim-该订单不是当前用户的, orderNo = {}, order.userId = {}, userId = {}",
                            orderNo, order.getUserId(), userId);
                    throw ServiceExceptionUtil.exception(ErrorCodeConstants.ES_ASSET_DEFAULT_ERROR, String.format("该订单不是当前用户的 [%s]", orderNo));
                }

                final List<Long> skuIdList = order.getItems().stream().map(v -> v.getSkuId()).collect(Collectors.toList());
                if (skuIdList.size() != 1) {
                    log.error("创建eSim-该订单的sku数据非法, orderNo = {}, skuIdList = {}", orderNo, skuIdList);
                    throw ServiceExceptionUtil.exception(ErrorCodeConstants.ES_ASSET_DEFAULT_ERROR, String.format("该订单的sku数据非法 [%s]", orderNo));
                }
                final Long skuId = skuIdList.get(0);

                // 查询sku
                log.info("创建eSim-查询sku, skuId = {}", skuId);
                final ProductSkuRespDTO sku = skuApi.getSku(skuId);
                final String esProductId = sku.getEsProductId();

                // 查询esProduct
                log.info("创建eSim-查询esProduct, esProductId = {}", esProductId);
                final EsProductDO esProduct = esProductService.getEsProduct(esProductId);

                // 请求esimart
                final CreateOrderReq req = new CreateOrderReq();
                req.setProductId(esProduct.getId());
                req.setReturnQR(true);
                req.setSellPrice(new BigDecimal(paymentPrice).multiply(new BigDecimal(100)));
                req.setBuyerEmail(buyerEmail);

                final CreateOrderRes createOrderRes;
                try {
                    log.info("创建eSim-请求esimart-开始, orderNo = {}", orderNo);
                    createOrderRes = simThirtyApiService.createESim(req);
                    log.info("创建eSim-请求esimart-成功, orderNo = {}, createOrderRes = {}", orderNo, createOrderRes);
                } catch (Exception e) {
                    log.error(String.format("创建eSim-请求esimart-异常, orderNo = %s", orderNo), e);
                    updateOrderDeliveryStatus(orderNo, false, e.getMessage());
                    throw e;
                }

                // 新增资产，并更新订单发货状态
                final EsAssetDO assetDO = insertAssetAndUpdateOrderStatus(createOrderRes, order, sku, esProduct);
                return assetDO.getId();
            }
        });

        log.info("创建eSim-完成, orderNo = {}, assetId = {}", orderNo, assetId);
        return assetId;
    }

    // 新增资产，并更新订单发货状态
    @Transactional(rollbackFor = Throwable.class)
    public EsAssetDO insertAssetAndUpdateOrderStatus(CreateOrderRes createOrderRes, TradeOrderRespDTO order,
                                                     ProductSkuRespDTO sku, EsProductDO esProduct) {
        // 新增资产
        final EsAssetDO assetDO = insertAsset(createOrderRes, order, sku, esProduct);
        log.info("创建eSim-新增资产, assetId = {}", assetDO.getId());

        // 更新订单发货状态
        final String orderNo = order.getNo();
        this.updateOrderDeliveryStatus(orderNo, true, null);

        return assetDO;
    }

    // 更新订单发货状态
    private void updateOrderDeliveryStatus(String orderNo, boolean success, String reason) {
        if (reason != null && reason.startsWith("java.lang.RuntimeException: ")) {
            reason = reason.replace("java.lang.RuntimeException: ", "");
        }
        if (success) {
            log.info("领取卡密-更新订单发货状态为成功, orderNo = {}, reason = {}", orderNo, reason);
        } else {
            log.info("领取卡密-更新订单发货状态为失败, orderNo = {}, reason = {}", orderNo, reason);
        }
        orderApiProxy.deliveryOrderByNo(orderNo, success, reason);
    }

    // 新增资产
    private EsAssetDO insertAsset(CreateOrderRes createOrderRes, TradeOrderRespDTO order,
                                  ProductSkuRespDTO sku, EsProductDO esProduct) {
        final EsAssetDO asset = new EsAssetDO();
        asset.setId(createOrderRes.getId());
        asset.setOrderNo(order.getNo());
        asset.setUserId(order.getUserId());
        asset.setSpuId(sku.getSpuId());
        asset.setSkuId(sku.getId());
        asset.setEsProductId(esProduct.getId());
        asset.setIccid(createOrderRes.getIccid());

        final String orderDateTimeUTC = createOrderRes.getOrderDateTimeUTC();
        final LocalDateTime esimartOrderDate = ESimUtil.convertToBeijingTime(orderDateTimeUTC);
        asset.setEsimartOrderDate(esimartOrderDate);

        asset.setQrCodeText(createOrderRes.getQrCode());

        final CheckUsageRes detail = createOrderRes.getDetail();

        // 使用量
        asset.setTotalDataSizeInMb(detail.getTotalDataSizeInMB());
        asset.setUsedDataSizeInMb(detail.getUsedDataSizeInMB());

        // 更新开始和结束日期
        final LocalDateTime startDate = ESimUtil.convertToBeijingTime(detail.getStartDateUTC());
        final LocalDateTime endDate = ESimUtil.convertToBeijingTime(detail.getEndDateUTC());
        asset.setStartDate(startDate);
        asset.setEndDate(endDate);

        esAssetMapper.insert(asset);
        return asset;
    }

    @Override
    public String getQrCode(Long userId, AppEsAssetGetQrCodeReqVO reqVO) {
        final String id = reqVO.getId();
        final EsAssetDO assetDO = validateEsAssetExists(id, userId);

        final String qrCodeText = assetDO.getQrCodeText();
        final String base64Image = QRCodeUtil.generateQRCodeBase64(qrCodeText, reqVO.getWidth(), reqVO.getHeight(), "png");
        return base64Image;
    }

}